package de.mnit.basis.crypt.cipher;

import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import de.mnit.basis.fehler.Fehler;
import de.mnit.basis.fehler.system.F_Sys_Sicherheit;
import de.mnit.basis.sys.thread.StreamPumpe;

/**
 * @author Michael Nitsche
 */
public class JavaCipher implements S_JavaCipher {

	private JAVA_CIPHER vg;


	public JavaCipher(JAVA_CIPHER vg) {
		Fehler.objekt.wenn_Null(vg);
		this.vg=vg;
	}


	public JAVA_CIPHER verfahren() {
		return vg;
	}

	public int schluesselLaengeMin() {
		return this.vg.laengeMin;
	}

	public int schluesselLaengeMax() {
		return this.vg.laengeMax;
	}

	public byte[] verschluesseln(byte[] text, byte[] schluessel) throws F_Sys_Sicherheit {
		return this.iText(text,schluessel,false);
	}

	public byte[] entschluesseln(byte[] text, byte[] schluessel) throws F_Sys_Sicherheit {
		return this.iText(text,schluessel,true );
	}

	public void verschluesseln(InputStream quelle, OutputStream ziel, byte[] schluessel, boolean warten) throws F_Sys_Sicherheit {
		this.iStream(quelle,ziel,schluessel,warten,false);
	}

	public void entschluesseln(InputStream quelle, OutputStream ziel, byte[] schluessel, boolean warten) throws F_Sys_Sicherheit {
		this.iStream(quelle,ziel,schluessel,warten,true );
	}

	// INTERN

	private void iPruefeSchluessel(byte[] schluessel) {
		Fehler.zahl.wenn_Ausserhalb(this.verfahren().laengeMin,this.verfahren().laengeMax,schluessel.length);
	}

	private void iStream(InputStream quelle, OutputStream ziel, byte[] schluessel, boolean warten, boolean entschluesseln) throws F_Sys_Sicherheit {
		Fehler.objekt.wenn_Null(quelle, ziel, schluessel);
		this.iPruefeSchluessel(schluessel);
	    CipherOutputStream cos = new CipherOutputStream(ziel, iCipher(schluessel, entschluesseln));
	    StreamPumpe.verbinde(quelle, cos, !warten);
	}

	private byte[] iText(byte[] text, byte[] schluessel, boolean entschluesseln) throws F_Sys_Sicherheit {
		Fehler.objekt.wenn_Null(text, schluessel);
		try {
			return iCipher(schluessel,entschluesseln).doFinal(text);
		}
		catch(IllegalBlockSizeException e) {
			Fehler.weitergeben(e);
		}
		catch(BadPaddingException e) {
			Fehler.weitergeben(e);
		}
	    throw Fehler.direkt("Fehler bei der Verschluesselung");
	}

	private Cipher iCipher(byte[] schluessel, boolean entschluesseln) throws F_Sys_Sicherheit {
		this.iPruefeSchluessel(schluessel);
		Cipher cipher=null;
		try {
		    SecretKey key = new SecretKeySpec( schluessel, vg.javaName );
		    cipher = Cipher.getInstance( vg.javaName );
		    cipher.init( entschluesseln ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE, key );
		}
		catch(NoSuchPaddingException e) {
			Fehler.weitergeben(e);
		}
		catch(NoSuchAlgorithmException e) {
			Fehler.weitergeben(e);
		}
		catch(InvalidKeyException e) {
			Fehler.weitergeben(e);
		}
	    return cipher;
	}

}
